package diskwatch

import (
	"os"
	"path/filepath"
	"slices"
	"sort"
	"strings"
	"time"

	"gitee.com/smewl13/beehive/pkg/config"
	"gitee.com/smewl13/beehive/pkg/lpath"
	"github.com/spf13/viper"
	"go.uber.org/zap"
)

func NewPathMonitors(logger *zap.Logger, conf *viper.Viper) []*PathMonitor {
	mps := lpath.MonutPoints()
	used := conf.GetFloat64("monitor.used")
	paths := conf.GetStringSlice("monitor.paths")
	extNames := conf.GetStringSlice("monitor.ext_names")
	isDelNullFile := conf.GetBool("monitor.is_del_null_file")
	expire := strings.TrimSpace(conf.GetString("monitor.expire"))
	retain := strings.TrimSpace(conf.GetString("monitor.retain"))
	retainMax := conf.GetInt("monitor.retain_max")
	retainMin := conf.GetInt("monitor.retain_min")

	expireDur, err := config.ParseDuration(expire)
	if err != nil {
		logger.Fatal(
			"[初始化][磁盘守护][加载配置]数据过期时长解析失败",
			zap.Any("paths", paths),
			zap.String("expire", expire),
			zap.Error(err),
		)
	}
	retainDur, err := config.ParseDuration(retain)
	if err != nil {
		logger.Fatal(
			"[初始化][磁盘守护][加载配置]数据保留时长解析失败",
			zap.Any("paths", paths),
			zap.String("retain", retain),
			zap.Error(err),
		)
	}
	if retainDur == 0 {
		retainDur = config.Duration(time.Minute) * 10
	}

	ens := make([]string, 0)
	for _, en := range extNames {
		en = strings.TrimSpace(en)
		if len(en) == 0 {
			continue
		}
		if !strings.HasPrefix(en, ".") {
			en = "." + en
		}
		ens = append(ens, en)
	}

	pms := make([]*PathMonitor, 0, len(paths))
	for _, path := range paths {
		path = strings.TrimSpace(path)
		if len(path) == 0 {
			continue
		}
		if !strings.HasPrefix(path, "/") || path == "/" {
			logger.Fatal(
				"[初始化][磁盘守护][加载配置]监控目录必须是绝对路径;不能是根目录",
				zap.Any("paths", paths),
				zap.String("path", path),
			)
		}
		pms = append(pms, &PathMonitor{
			monutPoint:    lpath.MatchMountPoint(path, mps),
			path:          path,
			isDelNullFile: isDelNullFile,
			used:          used,
			retainMax:     retainMax,
			retainMin:     retainMin,
			expireDur:     time.Duration(expireDur),
			retainDur:     time.Duration(retainDur),
			extNames:      ens,
			logger:        logger,
		})
	}
	return pms
}

type PathMonitor struct {
	monutPoint    string
	path          string
	isDelNullFile bool
	used          float64
	retainMax     int
	retainMin     int
	retainNum     int
	expireDur     time.Duration
	retainDur     time.Duration
	extNames      []string
	logger        *zap.Logger
}

func (pm PathMonitor) Handle(ch chan string) Cache {
	cache := pm.scan(ch)
	sort.Sort(cache)
	cache = pm.min(cache)
	cache = pm.max(cache, ch)
	return cache
}

func (pm *PathMonitor) min(cache Cache) Cache {
	num := pm.retainMin - pm.retainNum
	if pm.retainMin == 0 || num <= 0 || len(cache) == 0 {
		return cache
	}

	if len(cache) < num {
		return nil
	}
	return cache[:len(cache)-num]
}

func (pm *PathMonitor) max(cache Cache, ch chan string) Cache {
	num := pm.retainMax - pm.retainNum
	if pm.retainMin == 0 || num <= 0 || len(cache) == 0 || len(cache) < num {
		return cache
	}

	for _, fi := range cache[:num] {
		ch <- fi.Path
	}
	return cache[num:]
}

func (pm *PathMonitor) scan(ch chan string) Cache {
	des, err := os.ReadDir(pm.path)
	if err != nil {
		pm.logger.Error(
			"[磁盘守护][目录扫描]读取目录失败",
			zap.String("mount_point", pm.monutPoint),
			zap.String("path", pm.path),
			zap.Error(err),
		)
		return nil
	}
	pm.logger.Info(
		"[磁盘守护][目录扫描]读取目录",
		zap.String("mount_point", pm.monutPoint),
		zap.String("path", pm.path),
		zap.Int("总文件(目录)数", len(des)),
	)

	cache := make(Cache, 0, len(des))
	for _, de := range des {
		if de.IsDir() {
			continue
		}
		file := filepath.Join(pm.path, de.Name())
		fi, err := de.Info()
		if err != nil {
			pm.logger.Error(
				"[磁盘守护][目录扫描]读取文件信息失败",
				zap.String("mount_point", pm.monutPoint),
				zap.String("file", file),
				zap.Error(err),
			)
			continue
		}

		if pm.isDelNullFile && fi.Size() == 0 && time.Since(fi.ModTime()) >= pm.retainDur {
			ch <- file
			continue
		}
		extName := filepath.Ext(file)
		if len(pm.extNames) != 0 && slices.Contains(pm.extNames, extName) {
			continue
		}
		if pm.expireDur > 0 && time.Since(fi.ModTime()) >= pm.expireDur {
			ch <- file
			continue
		}
		if time.Since(fi.ModTime()) < pm.retainDur {
			pm.retainNum++
			continue
		}
		cache = append(cache, &FileInfo{Path: file, ModTime: fi.ModTime()})
	}
	return cache
}
